package com.opengroupe.util;

import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;

import com.opengroupe.dto.CandidatDto;
import com.opengroupe.entities.Candidat;
import com.opengroupe.entities.CandidatReponseQuestion;
import com.opengroupe.entities.Categorie;
import com.opengroupe.entities.Question;
import com.opengroupe.entities.Reponse;

/**
 * QRH Utilities class
 * 
 * @author MMB11447
 * 
 */
public final class QrhUtil {

	public final static String QRHUTIL_QUESTIONNAIRE = "questionnaire";
	public final static String QRHUTIL_QUESTION = "question";
	public final static String QRHUTIL_ID_QUESTION = "idQuestion";
	public final static String QRHUTIL_REPONSE = "reponse";
	public final static String QRHUTIL_CONTENU_Q = "contenuQ";
	public final static String QRHUTIL_CONTENU_QUESTION = "contenuQuestion";
	public final static String QRHUTIL_CONTENU_REPONSE = "contenuReponse";
	public final static String QRHUTIL_CATEGORIE = "categorie";
	public final static String QRHUTIL_REPONSESEVENTUELLES = "reponsesEventuelles";
	public final static String QRHUTIL_REPONSESCORRECTES = "reponsesCorrectes";
	public final static String QRHUTIL_PARAMETERS = "parameters";
	public final static String QRHUTIL_CANDIDAT = "candidat";
	public final static String QRHUTIL_EMAIL_CANDIDAT = "emailCandidat";

	/**
	 * Updates the specified map with the given values.
	 * 
	 * @param questionReponses
	 *            {@link Map} to be updated
	 * @param question
	 *            {@link Question} map's key
	 * @param reponse
	 *            {@link Reponse} to be added
	 */
	public static void updateMap(Map<Question, List<Reponse>> questionReponses,
			Question question, Reponse reponse) {
		if (questionReponses.containsKey(question)) {
			List<Reponse> reponses = questionReponses.get(question);
			reponses.add(reponse);
		} else {
			List<Reponse> reps = new ArrayList<Reponse>();
			reps.add(reponse);
			questionReponses.put(question, reps);
		}
	}
	
	/**
	 * Compare les reponses du candidat aux reponses correctes 
	 * des questions constituant le formulaire.
	 * 
	 * @param crq2 {@link List}
	 * @return {@link ResultatQuestionnaire}
	 */
	public static HeavyResultatQuestionnaire corrigerQuestions(List<CandidatReponseQuestion> crq2) {
		Map<Question, List<Reponse>> questionReponses = new HashMap<Question, List<Reponse>>();
		Candidat candidat = null;
		for(CandidatReponseQuestion crq: crq2) {
			if(candidat == null) {
				candidat = crq.getCandidat();
			}
			
			Question question = crq.getQuestion();
			Reponse reponse = crq.getReponse();
			QrhUtil.updateMap(questionReponses, question, reponse);
		}

		Map<Question, Boolean> resultat = QrhUtil.corrigerQuestionnaire(questionReponses);
		return new HeavyResultatQuestionnaire(candidat, questionReponses, resultat);
	}

	/**
	 * Corrects the "questionnaire" by comparing candidate's response with the
	 * correct ones linked to the {@link Question}
	 * 
	 * @param questionReponses
	 *            each {@link Question} is attached to set of {@link Reponse}
	 * @return each {@link Question} is associated to <code>true</code> if
	 *         {@link Reponse} is the good one. <code>false</code> otherwise
	 */
	public static Map<Question, Boolean> corrigerQuestionnaire(
			Map<Question, List<Reponse>> questionReponses) {
		Map<Question, Boolean> resultat = new HashMap<Question, Boolean>();
		for (Map.Entry<Question, List<Reponse>> entry : questionReponses
				.entrySet()) {
			Question question = entry.getKey();
			List<Reponse> reponses = entry.getValue();
			boolean correct = isEqual(new ArrayList<Reponse>(question.getReponsesCorrectes()),
					reponses);
			resultat.put(question, new Boolean(correct));
		}

		return resultat;
	}

	/**
	 * Retrieves the {@link Reponse} in the collection which has the given
	 * contenuReponse
	 * 
	 * @param reponses
	 *            collection of {@link Reponse}
	 * @param contenuReponse
	 *            content to retrieve
	 * @return {@link Reponse}
	 */
	public static Reponse findReponse(List<Reponse> reponses,
			String contenuReponse) {
		for (Reponse reponse : reponses) {
			if (reponse.getContenuReponse().equalsIgnoreCase(contenuReponse)) {
				return reponse;
			}
		}
		return null;
	}

	/**
	 * <p>
	 * Checks if two calendars represent the same day ignoring time.
	 * </p>
	 * 
	 * @param cal1
	 *            the first calendar, not altered, not null
	 * @param cal2
	 *            the second calendar, not altered, not null
	 * @return true if they represent the same day
	 * @throws IllegalArgumentException
	 *             if either calendar is <code>null</code>
	 */
	public static boolean isSameDay(Calendar cal1, Calendar cal2) {
		if (cal1 == null || cal2 == null) {
			throw new IllegalArgumentException("The dates must not be null");
		}
		return (cal1.get(Calendar.ERA) == cal2.get(Calendar.ERA)
				&& cal1.get(Calendar.YEAR) == cal2.get(Calendar.YEAR) && cal1
					.get(Calendar.DAY_OF_YEAR) == cal2
				.get(Calendar.DAY_OF_YEAR));
	}

	/**
	 * Generates a new access code matching this pattern
	 * {qRH[0-9]{3}T[0-2][0-3][0-5][0-9]}
	 * 
	 * @return
	 */
	public static String generateAccessCode() {
		StringBuffer sbf = new StringBuffer("qRH");

		for (int index = 0; index < 3; index++) {
			sbf.append(randomize());
		}
		sbf.append("T");
		sbf.append(getTime());

		return sbf.toString();
	}

	@SuppressWarnings("rawtypes")
	public static List<Question> importQuestions(InputStream input)
			throws IOException, JDOMException {
		SAXBuilder builder = new SAXBuilder();
		Document document = (Document) builder.build(input);
		
		Element rootNode = document.getRootElement();
		List questionElts = rootNode.getChildren();
		List<Question> questions = new ArrayList<Question>(questionElts.size());
		
		for(int index=0; index<questionElts.size(); index++) {
			Element curElement = (Element) questionElts.get(index);
			Question question = new Question();
			question.setContenuQuestion(curElement
					.getChildText(QRHUTIL_CONTENU_QUESTION));

			Categorie categorie = new Categorie();
			categorie.setLibelleCategorie(curElement.getChildText(QRHUTIL_CATEGORIE));
			question.setCategorie(categorie);

			Element reponses = curElement.getChild(QRHUTIL_REPONSESEVENTUELLES);
			List list = reponses.getChildren(QRHUTIL_REPONSE);
			int size = list.size();
			List<Reponse> qReponses = new ArrayList<Reponse>(size);
			for (int i = 0; i < size; i++) {
				Reponse reponse = new Reponse();
				Element node = (Element) list.get(i);
				reponse.setContenuReponse(node
						.getChildText(QRHUTIL_CONTENU_REPONSE));
				qReponses.add(reponse);
			}
			question.setReponsesEventuelles(qReponses);

			reponses = curElement.getChild(QRHUTIL_REPONSESCORRECTES);
			list = reponses.getChildren(QRHUTIL_REPONSE);
			size = list.size();
			qReponses = new ArrayList<Reponse>(size);
			for (int i = 0; i < size; i++) {
				Reponse reponse = new Reponse();
				Element node = (Element) list.get(i);
				reponse.setContenuReponse(node
						.getChildText(QRHUTIL_CONTENU_REPONSE));
				qReponses.add(reponse);
			}
			question.setReponsesCorrectes(qReponses);
			
			questions.add(question);
		}

		return questions;
	}

	/**
	 * Randomizes a number between 0-9.
	 * 
	 * @return {@link Integer}
	 */
	private static int randomize() {
		int lower = 0, higher = 9, rand;
		rand = (int) (Math.random() * (higher + 1 - lower)) + lower;

		return rand;
	}

	/**
	 * Returns the current time.
	 * 
	 * @return {@link String} current time string format
	 */
	private static String getTime() {
		SimpleDateFormat sdf = new SimpleDateFormat("HHmm");
		return sdf.format(new Date());
	}

	/**
	 * Returns <code>true</code> if the two sets contains the same
	 * {@link Reponse}. <code>false</code> otherwise.
	 * 
	 * @param reponsesCorrectes
	 *            initial values
	 * @param reponses
	 *            to be compared
	 * @return <code>true</code> or <code>false</code>
	 */
	private static boolean isEqual(List<Reponse> reponsesCorrectes,
			List<Reponse> reponses) {
		if (reponsesCorrectes.size() != reponses.size()) {
			return false;
		}

		return reponsesCorrectes.containsAll(reponses)
				&& reponses.containsAll(reponsesCorrectes);
	}
	

	public static void deleteLastChar(StringBuffer sbf) {
		int index = sbf.toString().lastIndexOf(",");
		if(index == sbf.length() - 1) {
			sbf.deleteCharAt(index);
		}
	}
	
	public static CandidatDto convertCandidatToCandidatDto(Candidat candidat) {
		CandidatDto candidatDto = new CandidatDto();
		candidatDto.setIdCandidat(candidat.getIdCandidat());
		candidatDto.setEmailCandidat(candidat.getEmailCandidat());
		candidatDto.setNomCandidat(candidat.getNomCandidat());
		candidatDto.setPrenomCandidat(candidat.getPrenomCandidat());
		candidatDto.setTelephoneCandidat(candidat.getTelephoneCandidat());
		return candidatDto;
	}

}
